#include "qtch/db/sqlite.h"
#include "qtch/util.h"
#include "qtch/log.h"

namespace qtch {

Logger::ptr logger = QTCH_LOG_NAME("system");

SQLite3Res::SQLite3Res(std::shared_ptr<SQLite3Stmt> res) {
    m_first = true;
    m_data = res;
    int column_count = getColumnCount();
    for(int i = 0; i < column_count; ++i){
        std::string column_name = getColumnName(i);
        m_cloumn_name_index[column_name] = i;
    }

}

int SQLite3Res::getErrno() const {
    return m_data->getError();
}

const std::string SQLite3Res::getErrStr() const {
    return m_data->getErrStr();
}

int SQLite3Res::getDataCount() {
    return sqlite3_data_count(m_data->m_stmt);
}

int SQLite3Res::getColumnCount() {
    return sqlite3_column_count(m_data->m_stmt);
}

int SQLite3Res::getColumnBytes(int idx) {
    return sqlite3_column_bytes(m_data->m_stmt,idx);
}

int SQLite3Res::getColumnType(int idx) {
    return sqlite3_column_type(m_data->m_stmt,idx);
}

int SQLite3Res::getColumnIdx(const std::string& columnName) {
    auto it = m_cloumn_name_index.find(columnName);
    if(it == m_cloumn_name_index.end()){
        return -1;
    }
    return it->second;
}

int SQLite3Res::getColumnIdx(const char* columnName) {
    return getColumnIdx(columnName);
}

std::string SQLite3Res::getColumnName(int idx) {
    const char * column_name = sqlite3_column_name(m_data->m_stmt,idx);
    std::string name(column_name);
    return name;
}

bool SQLite3Res::isNull(int idx) {
    return getColumnType(idx) == SQLITE_NULL;
}

int8_t SQLite3Res::getInt8(int idx) {
    return getInt64(idx);
}

uint8_t SQLite3Res::getUint8(int idx) {
    return getInt64(idx);
}

int16_t SQLite3Res::getInt16(int idx) {
    return getInt64(idx);
}

uint16_t SQLite3Res::getUint16(int idx) {
    return getInt64(idx);
}

int32_t SQLite3Res::getInt32(int idx) {
    return getInt64(idx);
}

uint32_t SQLite3Res::getUint32(int idx) {
    return getInt64(idx);
}

int64_t SQLite3Res::getInt64(int idx) {
    return sqlite3_column_int64(m_data->m_stmt, idx);
}

uint64_t SQLite3Res::getUint64(int idx) {
    return getInt64(idx);
}

float SQLite3Res::getFloat(int idx) {
    return getDouble(idx);
}

double SQLite3Res::getDouble(int idx) {
    return sqlite3_column_double(m_data->m_stmt, idx);
}

std::string SQLite3Res::getString(int idx) {
    const unsigned char * value = sqlite3_column_text(m_data->m_stmt, idx);
    std::string v((char *)value,getColumnBytes(idx));
    return v;
}

std::string SQLite3Res::getBlob(int idx) {
    const void * value = sqlite3_column_blob(m_data->m_stmt, idx);
    std::string v((char *)value,getColumnBytes(idx));
    return v;
}

time_t SQLite3Res::getTime(int idx) {
    std::string value = getString(idx);
    return Str2Time(value.c_str());
}

bool SQLite3Res::isNull(const std::string& name) {
    int idx = getColumnIdx(name);
    return isNull(idx);
}

int8_t SQLite3Res::getInt8(const std::string& name) {
    int idx = getColumnIdx(name);
    return getInt64(idx);
}

uint8_t SQLite3Res::getUint8(const std::string& name) {
    int idx = getColumnIdx(name);
    return getInt64(idx);
}

int16_t SQLite3Res::getInt16(const std::string& name) {
    int idx = getColumnIdx(name);
    return getInt64(idx);
}

uint16_t SQLite3Res::getUint16(const std::string& name) {
    int idx = getColumnIdx(name);
    return getInt64(idx);
}

int32_t SQLite3Res::getInt32(const std::string& name) {
    int idx = getColumnIdx(name);
    return getInt64(idx);
}

uint32_t SQLite3Res::getUint32(const std::string& name) {
    int idx = getColumnIdx(name);
    return getInt64(idx);
}

int64_t SQLite3Res::getInt64(const std::string& name) {
    int idx = getColumnIdx(name);
    return getInt64(idx);
}

uint64_t SQLite3Res::getUint64(const std::string& name) {
    int idx = getColumnIdx(name);
    return getInt64(idx);
}

float SQLite3Res::getFloat(const std::string& name) {
    int idx = getColumnIdx(name);
    return getDouble(idx);
}

double SQLite3Res::getDouble(const std::string& name) {
    int idx = getColumnIdx(name);
    return getDouble(idx);
}

std::string SQLite3Res::getString(const std::string& name) {
    int idx = getColumnIdx(name);
    return getString(idx);
}

std::string SQLite3Res::getBlob(const std::string& name) {
    int idx = getColumnIdx(name);
    return getBlob(idx);
}

time_t SQLite3Res::getTime(const std::string& name) {
    int idx = getColumnIdx(name);
    return getTime(idx);
}

bool SQLite3Res::next() {
    
    int rt = sqlite3_step(m_data->m_stmt);
    if(m_first){
        m_first = false;
    }
    return rt == SQLITE_ROW;
}


SQLite3Stmt::ptr SQLite3Stmt::Create(std::shared_ptr<SQLite3> db,const std::string & stmt) {
    return SQLite3Stmt::ptr(new SQLite3Stmt(db,stmt));
}

SQLite3Stmt::~SQLite3Stmt() {
    sqlite3_finalize(m_stmt);
}

int SQLite3Stmt::bindInt8(int idx, const int8_t& value) {
    return bindInt64(idx,value);
}

int SQLite3Stmt::bindUint8(int idx, const uint8_t& value) {
    return bindInt64(idx,value);
}

int SQLite3Stmt::bindInt16(int idx, const int16_t& value) {
    return bindInt64(idx,value);
}

int SQLite3Stmt::bindUint16(int idx, const uint16_t& value) {
    return bindInt64(idx,value);
}   

int SQLite3Stmt::bindInt32(int idx, const int32_t& value) {
    return bindInt64(idx,value);
}

int SQLite3Stmt::bindUint32(int idx, const uint32_t& value) {
    return bindInt64(idx,value);
}

int SQLite3Stmt::bindInt64(int idx, const int64_t& value) {
    return sqlite3_bind_int64(m_stmt,idx,value);
}

int SQLite3Stmt::bindUint64(int idx, const uint64_t& value) {
    return bindInt64(idx,value);
}

int SQLite3Stmt::bindFloat(int idx, const float& value) {
    return bindDouble(idx,value);
}

int SQLite3Stmt::bindDouble(int idx, const double& value) {
    return sqlite3_bind_double(m_stmt,idx,value);
}

int SQLite3Stmt::bindString(int idx, const char * value) {
    return bindString(idx,value);
    
}

int SQLite3Stmt::bindString(int idx, const std::string& value) {
    return sqlite3_bind_text(m_stmt, idx ,value.c_str(), value.size(), SQLITE_TRANSIENT);
}

int SQLite3Stmt::bindBlob(int idx, const char* value, int64_t size) {
    return sqlite3_bind_blob(m_stmt, idx, value, size, SQLITE_TRANSIENT);
}

int SQLite3Stmt::bindBlob(int idx, const std::string& value) {
    return bindBlob(idx, value.c_str(), value.size());
}

int SQLite3Stmt::bindTime(int idx, const time_t& value) {
    return bindString(idx,Time2Str(value, "%Y-%m-%d %H:%M:%S"));
}

int SQLite3Stmt::bindNull(int idx) {
    return sqlite3_bind_null(m_stmt,idx);
}

int SQLite3Stmt::execute() {

    int rt = sqlite3_step(m_stmt);
    if(rt != SQLITE_DONE){
        return 0;
    }
    QTCH_LOG_DEBUG(logger) << "execute step rt:" << rt;
    return 1;
}

int64_t SQLite3Stmt::getLastInsertId() {
    return -1;
}

ISQLData::ptr SQLite3Stmt::query() {
    int rt = sqlite3_step(m_stmt);
    if(rt != SQLITE_ROW){
        QTCH_LOG_DEBUG(logger) << "sqlite3 query{" << sqlite3_sql(m_stmt) << "} faile," << m_sqlite3->getErrStr();
        m_error = m_sqlite3->getError();
        m_errmsg = m_sqlite3->getErrStr();
        return nullptr;
    }
    sqlite3_reset(m_stmt);
    SQLite3Res::ptr res_ptr = SQLite3Res::ptr(new SQLite3Res(shared_from_this()));
    return res_ptr;
}

void SQLite3Stmt::setSqlSentence(const std::string& sql) {
    m_sql = sql;
}

int SQLite3Stmt::getError() {
    return m_error;
}

std::string SQLite3Stmt::getErrStr() {
    return m_errmsg;
}

SQLite3Stmt::SQLite3Stmt(std::shared_ptr<SQLite3> db,const std::string & stmt){
    m_sqlite3 = db;
    m_sql = stmt;
    m_stmt = NULL;
    int rt = sqlite3_prepare_v3(db->getConn().get(),m_sql.c_str(),m_sql.size(), 0, &m_stmt,NULL);
    if(rt != SQLITE_OK){
        QTCH_LOG_ERROR(logger) << "sqlite3_prepare_v3 faile, error:" << rt << " errmsg:" << sqlite3_errstr(rt);
    }
}

SQLite3::ptr SQLite3::Create(const std::string& name, const std::string& filePath, int flag){
    sqlite3 * conn;
    int rt = sqlite3_open_v2(filePath.c_str(),&conn, flag, NULL);
    if(rt != SQLITE_OK || !(conn)){
        QTCH_LOG_ERROR(logger) << "can't open SQLite3 name:" << name << " filePath:" << filePath
            << " errcode:" << rt << " errmsg:" <<  sqlite3_errstr(rt);
        return nullptr;
    }
    SQLite3::ptr res_ptr = SQLite3::ptr(new SQLite3(name,conn));
    return res_ptr;

}

SQLite3* SQLite3::CreateNoPtr(const std::string& name, const std::string& filePath, int flag){
    sqlite3 * conn;
    int rt = sqlite3_open_v2(filePath.c_str(),&conn, flag, NULL);
    if(rt != SQLITE_OK || !(conn)){
        QTCH_LOG_ERROR(logger) << "can't open SQLite3 name:" << name << " filePath:" << filePath
            << " errcode:" << rt << " errmsg:" <<  sqlite3_errstr(rt);
        return nullptr;
    }
    SQLite3* db = new SQLite3(name,conn);
    return db;

}

SQLite3::SQLite3(const std::string& name, sqlite3* conn){
    m_dbname = name;
    m_dbPath = sqlite3_db_filename(conn,NULL);
    m_conn.reset(conn,sqlite3_close_v2);
}

int64_t SQLite3::getLastInsertId() {
    return -1;
}
IStmt::ptr SQLite3::prepare(const std::string& stmt) {
    return SQLite3Stmt::Create(shared_from_this(), stmt);
}

int SQLite3::getError() {
    return sqlite3_errcode(m_conn.get());
}

std::string SQLite3::getErrStr() {
    if(!m_conn){
        return "sqlite is null";
    }
    return sqlite3_errmsg(m_conn.get());
}

ITransaction::ptr SQLite3::openTransaction(bool auto_commit) {
    return SQLite3Transaction::Create(shared_from_this(),auto_commit);
}

ISQLData::ptr SQLite3::query(const char* format, ...) {
    va_list ap;
    va_start(ap,format);
    auto rt=query(format,ap);
    va_end(ap);
    return rt;

}

ISQLData::ptr SQLite3::query(const char* format, va_list ap) {
    std::string sql = StringUtil::Formatv(format,ap);
    return query(sql);
}

ISQLData::ptr SQLite3::query(const std::string& sql) {
    SQLite3Stmt::ptr res = SQLite3Stmt::Create(shared_from_this(),sql);
    qtch::ISQLData::ptr rt = res->query();
    return rt;
}

int SQLite3::execute(const char* format, ...) {
    va_list ap;
    va_start(ap,format);
    auto rt=execute(format,ap);
    va_end(ap);
    return rt;
}

int SQLite3::execute(const char* format, va_list ap){
    std::string sql = StringUtil::Formatv(format,ap);
    return execute(sql);
}

int SQLite3::execute(const std::string& sql) {
    SQLite3Stmt::ptr res = SQLite3Stmt::Create(shared_from_this(),sql);
    return res->execute();
}

SQLite3Transaction::ptr SQLite3Transaction::Create(SQLite3::ptr db,bool autoCommit) {
    return SQLite3Transaction::ptr(new SQLite3Transaction(db,autoCommit));
}

SQLite3Transaction::SQLite3Transaction(SQLite3::ptr db,bool autoCommit){
    m_db = db;
    m_autoCommit = autoCommit;
    m_isfinish = true;
    m_hasError = false;
}

SQLite3Transaction::~SQLite3Transaction() {
    if(m_autoCommit){
        commit();
    } else {
        rollback();
    }
}

bool SQLite3Transaction::begin() {
    if(!m_isfinish){
        QTCH_LOG_ERROR(logger) << "postgresql already in Transaction";
        return false;
    }
    m_isfinish = false;
    int rt = execute("BEGIN;");
    return rt;
}

bool SQLite3Transaction::commit() {
     if(m_isfinish || m_hasError){
        return !m_hasError;
    }
    int rt = execute("COMMIT;");
    if(!rt){
        m_hasError = true;
    } else{
        m_isfinish = true;
    }
    return rt;
}

bool SQLite3Transaction::rollback() {
    if(m_isfinish){
        return true;
    }
    int rt = execute("ROLLBACK;");
    if(!rt){
        m_hasError = true;
    } else{
        m_isfinish = true;
    }
    return rt;
}

int SQLite3Transaction::execute(const char* format, ...) {
    va_list ap;
    va_start(ap,format);
    int rt = execute(format,ap);
    va_end(ap);
    return rt;
}

int SQLite3Transaction::execute(const char* format, va_list ap) {
    std::string sql = StringUtil::Formatv(format,ap);
    return execute(sql);
}

int SQLite3Transaction::execute(const std::string& sql) {
    return m_db->execute(sql);
}

int64_t SQLite3Transaction::getLastInsertId() {
    return -1;
}

SQLite3Manager::SQLite3Manager() {
    m_maxConn = 10;
}

SQLite3Manager::~SQLite3Manager() {
    for(auto& i : m_dbs){
        for(auto& j : i.second){
            delete j;
        }
    }
}

SQLite3::ptr SQLite3Manager::get(const std::string& name) {
    MutexType::Lock lock(m_mutex);
    auto list_db = m_dbs.find(name);
    if(list_db != m_dbs.end() && !list_db->second.empty()){
        
        SQLite3* db = list_db->second.front();
        list_db->second.pop_front();
        lock.unlock();
        return SQLite3::ptr(db,std::bind(&SQLite3Manager::freeDB, this, name, std::placeholders::_1));
    }

    auto config = m_filePaths.find(name);
    if(config == m_filePaths.end()){
        QTCH_LOG_ERROR(logger) << "sqlite3 can't get database name:" << name;
        return nullptr;
    }
    SQLite3* db = SQLite3::CreateNoPtr(name,config->second);
    if(!db){
        QTCH_LOG_ERROR(logger) << "sqlite3 connect database faile, name:" << name
            << " filePath:" << config->second;
        return nullptr;
    }
    return SQLite3::ptr(db,std::bind(&SQLite3Manager::freeDB, this, name, std::placeholders::_1));
}

void SQLite3Manager::registerSQLite3(const std::string& name,const std::string& filePath) {
    if(name.empty() || filePath.empty()){
        return;
    }
    m_filePaths[name] = filePath;
}

ISQLData::ptr SQLite3Manager::query(const std::string& name,const char* format, ...) {
    va_list ap;
    va_start(ap,format);
    ISQLData::ptr rt = query(name,format,ap);
    va_end(ap);
    return rt;
}

ISQLData::ptr SQLite3Manager::query(const std::string& name,const char* format, va_list ap) {
    std::string sql = StringUtil::Formatv(format,ap);
    return query(name,sql);
}

ISQLData::ptr SQLite3Manager::query(const std::string& name,const std::string& sql) {
    SQLite3::ptr sqlite_ptr = get(name);
    if(!sqlite_ptr){
        return nullptr;
    }
    return sqlite_ptr->query(sql);
}

int SQLite3Manager::execute(const std::string& name,const char* format, ...) {
    va_list ap;
    va_start(ap,format);
    int rt = execute(name,format,ap);
    va_end(ap);
    return rt;
}

int SQLite3Manager::execute(const std::string& name,const char* format,va_list ap) {
    std::string sql = StringUtil::Formatv(format,ap);
    return execute(name,sql);
}

int SQLite3Manager::execute(const std::string& name,const std::string& sql) {
    SQLite3::ptr sqlite_ptr = get(name);
    if(!sqlite_ptr){
        return 0;
    }
    return sqlite_ptr->execute(sql);
}

void SQLite3Manager::freeDB(const std::string& name,SQLite3* m){
    QTCH_LOG_DEBUG(logger) << "free SQLite3 name=" << name;
    if(m){
        Mutex::Lock lock(m_mutex);
        if(m_dbs[name].size() < (size_t) m_maxConn){
            m_dbs[name].push_back(m);
            return;
        }
    }
    delete m;
}



}



